There are a hundred and one proposed solutions out there for how to move UITextField
and UITextView
out of the way of the keyboard during editing — usually, it comes down to observing UIKeyboardWillShowNotification
and UIKeyboardWillHideNotification
, or implementing UITextFieldDelegate
delegate methods, and adjusting the frame of the superview, or using UITableView
‘s scrollToRowAtIndexPath:atScrollPosition:animated:
, but all the proposed solutions I’ve found tend to be quite DIY, and have to be implemented for each view controller that needs it.
I thought I’d put together a relatively universal, drop-in solution: UIScrollView
and UITableView
subclasses that handle everything.
When the keyboard is about to appear, the subclass will find the subview that’s about to be edited, and adjust its frame and content offset to make sure that view is visible, with an animation to match the keyboard pop-up. When the keyboard disappears, it restores its prior size.
It should work with basically any setup, either a UITableView-based interface, or one consisting of views placed manually.
For non-UITableViewControllers, use it as-is by dropping the TPKeyboardAvoidingScrollView
source files into your project, popping a UIScrollView
into your view controller’s xib, setting the class to TPKeyboardAvoidingScrollView
, and putting all your controls within that scroll view.
To use it with UITableViewController, pop the TPKeyboardAvoidingTableView
source files in, and just make your UITableView a TPKeyboardAvoidingTableView
in the xib — everything should be taken care of.
You can grab the source files, which includes a sample project, over on the [GitHub project page](https://github.com/michaeltyson/TPKeyboardAvoiding)
Michael, this is a really usefull class, however, I discovered unusual behavior when using your class on an iPad. It seems to extend the scroll view too much and thus, creates unusual behavior when selecting a textfield, as the scroll view will scroll up even though it is not necessary (i.e. when the keyboard does not cover the text field). Is there a workaround for this?
Also, is it possible to have the view containing the textfields to reposition itself to the position it was before the keyboard appears?
EDIT: I wanted to add to my comment, that the iPad orientation seems to have something to do. I have my xib file set to portrait mode, when running the App on the iPad and turning it to landscape mode, the size of the scroll view seems to be the original height of the portrait view, and not the height of the landscape view, which is what we want, right?.
Benjamin,
To handle orientation changes properly, you need to set the “Autosizing” properties of the UIScrollView/TPKeyboardAvoidingScrollView.
I’ve put a screenshot here.
Hi, thanks for the response. My scrollview is already configured that way, I have attached a screenshot showing my problem. Notice the scroll Indicator, It shows extra space on the bottom, but it’s not supposed to do that :( The content shown on the picture is all the content I have made for that view
http://imageshack.us/photo/my-images/543/screenshot20120216at859.png/
Fantastic class. Been a great time saver, so easy to utilise. And saves me bloating each of my classes with countless delegate methods!
This is truly amazing, I’ve been trying different methods with no success, especially on TextViews. Thank you so much!
Thanks for this! This was extremely useful! :)
First off thanks for the enlightening clarity on this often discussed (and often confusing) topic. I find your classes very useable. One thing though, I too experienced some jumping/jerky animation of the keyboard at times, along with the scroll view landing (what appeared to be) back where it started.
I found a race condition between when the NSNotifications are delivered and when a textField (or textView) didBeginEditing method got invoked by the system. Recall it is these methods our apps are to then call into the TP class’s [scrollView adjustOffsetToIdealIfNeeded];
The problem looks to be setting of the _keyboardVisible boolean too early, as in when the nsnotification arrives and prior to the initial keyboard scroll animation visually completing.
The fix I came up with is reworking the animation inside the notification to use blocks, and also supply a completion handler. It is only in the completion handler where I then set _keyboardVisible = YES. Waiting for the initial scroll to complete before setting the flag, as opposed to before, allows the other code looking at _keyboardVisible to behave more accurately.
Steps to repro: – have a xib/viewcontroller with a few TextFields and a TextView near the bottom. – click in the lowest TextView, causing the initial appearance of keyboard – scrolling jumps/jerks
xCode 4.2, running iPhone simulator 5.0
Michael
Hopefully I’ve conformed to the submission process correctly, as I’ve enclosed the animation code for the notification method keyboardWillShow below.
`
float animDur = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; self.contentInset = [self contentInsetForKeyboard]; CGPoint calcPoint = CGPointMake(self.contentOffset.x, [self idealOffsetForView:firstResponder withSpace:[self keyboardRect].origin.y - self.bounds.origin.y]);
`
Brilliant work, Michael, thanks so much – I love it when a solution is provided ;-) I’ll check this out tomorrow and add it to the github project.
Has this jumpiness been fixed in the latest code on Github? I downloaded it on June 28 and it is still behaves like that.
It would be great to get a fix, and that should make this a fantastic tool in every iOS developer’s toolkit.
Thanks.
ACtually, nevermind, the issue only happens in the iPhone Simulator when I hit the tab key. This is not a real issue with the device.
Great job and thanks for sharing it with the community.
A
It doesn’t work for me….Unknown class TPKeyboardAvoidingScrollView in Interface Builder file?
Hi, I have the same problem. Have you solved? Thanks
I think that I am having the same problem. I grabbed the code last night.
Here is the exact error: 2012-04-16 17:54:01.490 Moto Log[24021:1a303] Unknown class TPKeyboardAvoidingScrollView in Interface Builder file. 2012-04-16 17:54:01.493 Moto Log[24021:1a303] 0 2012-04-16 17:54:01.493 Moto Log[24021:1a303] 1 2012-04-16 17:54:01.494 Moto Log[24021:1a303] 2 2012-04-16 17:54:03.367 Moto Log[24021:1a303] -[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0x8eef060 2012-04-16 17:54:03.367 Moto Log[24021:1a303] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0x8eef060’
Make sure that when you add the .h and .m files, the “Add to targets” checkbox is marked, that solved my problem.
Great job. Simple and elegant. Perfect.
Thank you!
Great Job!!! Useful and very well done!! Thank a lot for sharing!!
What to do when i use a custom cell, the cell is 80% of the size off the screen, the top textfield (active field) is scrolled off screen. but needs to be visible, and i want to change the TPkeyboardAvoidingTableView to be an UITableViewController, so i can add some of the managed objects to the class.
Great solution but doesnt work with the ipad on landscape mode! please help!
This simply doesn’t work for me. I’ve been struggling with it: First, the UITextFieldDelegate methods weren’t called, but I realized I needed to connect each text field’s delegate to File’s Owner in IB. The methods are now called.
However, now when adjustOffsetToIdeaIfNeeded is called I get an Invalid Argument crash: -[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0xe879030 2012-07-05 11:10:30.348 WizardTest[25765:15203] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0xe879030’
This seems to point to trying to call this method on UIScrollView instead of TPKeyboardAvoidScrollView, but I’ve set the scroll view type to TPKeyboard… in my view class, and set the scroll view’s class to TPKeyboard… in IB (Custom Class). Everything else looks pretty much the same in my app compared to the sample app, except that my scroll view is created in IB but manually added to the view with addSubView. (Changing it to add it in IB doesn’t help, though.)
Any help would be appreciated.
Excuse me but i think i have exactly your problem, the scrollView control thinks that it have to call a method in UIScrollView instead TPKeyboardAvoidScrollView, i think the problem is that IB dosent change the class even if you put it manually in the outlet creation dialog box. Did you get the solution? Thanks a lot.
Im going to answer my self because i found the problem, when the outlet is created you have to give it the type in this case TPKeyboardAvoidScrollView, the step that i missing is that i have to select the scrollView and in the identity inspector in the custom class field also have to be filled with TPKeyboardAvoidScrollView. Now it works just fine!
Tnx to Michael Tyson by the way!
Dude, this library is sick! So easy to use–well done.
Thanks M J!
receiving error like this Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key viewScroll. what is that about? thanks anyway! :)
First of all, I found this post really awesome. There is only one minor issue that I can see when I enable “Correction” on the UITextView. Once typing begins, it shows some odd flicker of pixels on the top of the keyboard. Is there something I can do to avoid this problem other than disabling “Correction” ?
Michael, Thanks for this terrific class. I’m using in an IOS 5.1 project using Storyboards and ARC. The library works just fine but with two small differences from the sample project. I don’t get the “Next” or “Done” button when moving thru the fields in the Scrollview. I’m using the TPKeyboardAvoidingScrollView class. I’m probably missing something really trivial. Any suggestions?
Thanks again so much for this class! Ken
Like I said it was trivial. I had forgotten to change the appropriate property value in IB. Sorry to bug you.
I’m having the same problem … can’t seem to figure out this trivial issue.
Nevermind … you’re right. Very trivial. Just had to select the correct delegate. Opps!
Having an issue. Not using IB to build anything, just code. Here’s my block to screate the scrollview:
TPKeyboardAvoidingScrollView *screen = [[TPKeyboardAvoidingScrollView alloc] initWithFrame:rect]; screen.backgroundColor = [UIColor clearColor]; screen.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; screen.autoresizesSubviews = YES; screen.showsHorizontalScrollIndicator = NO;
Then I add the TextField as a subview:
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(40.0f, 431.0f, 460.0f, 45.0f)]; textField.delegate = self; [textField setPlaceholder:@”Email Address”]; textField.returnKeyType = UIReturnKeyDone; [screen addSubview:textField];
My View Controller is registered as a UITextFieldDelegate, and the following function IS being called when I click ‘Done’ on the keyboard:
I have also verified that this textField IS the firstResponder at the time of the ‘Done’ click, so I’m definitely resigning the textField from a firstResponder state.
This is on an iPad-only app. Thank you so much for any help.
Apologies. Forgot to mention what the problem was, haha. The keyboard isn’t being dismissed, even though it should since resignFirstResponder is being called.
Apologies. This is NOT a TPKeyboard… issue. Put the following into your View Controller if the View Controller is presented modally.
Thank you. Simply works and very straight forward to implement.
Thank you so much! It’s nice to see people that know how to make self contained code. You definitely save me some headaches.
Thanks a lot for this, really well coded and simple to use.
Amazing. There is only one minor issue that I can see when I enable “Correction” on the UITextView.
Thanks for the great script. Saved me an hour or five :-)
I am experiencing an issue with iOS 6 and AutoLayouts. I see this error:
*** Assertion failure in -[TPKeyboardAvoidingTableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2372/UIView.m:5776
Has anyone else seen this? I don’t want to not use Auto-Layout to be the solution. I hope this can be fixed. I would really appreciate it.
the situation: you have a scroll view with 2 text fields the issue: 1. click the second text field (keyboard goes up) 2. click the first text field (keyboard goes back) 3. click on the scroll view (keyboard dismisses); but the content offset is skewed
I’m not sure if this is the right solution, but commenting the following line in the method -(void)scrollToActiveTextField solves the issue.
line _originalContentOffset = self.contentOffset;
P.S. my original insets are 0. reproduces in ios 5 and 6.
Please let me know if there are any better way to fix the issue.
Very nice solution and elegant. Thanks
Thanks for this solution! Of the 5-6 I found, yours worked the best and easiest.
Hi Michael. Thank you for sharing this. It’s a real lifesaver class.
I’ve noticed that the TPKeyboardAvoiding view doesn’t scroll until I select a textview and tap to close the keyboard. I can’t scroll the view before. Is this normal?. Can I force the view to act like a regular scrollview at loading?
Thank you
Hi Ricardo – actually, this is ordinary UIScrollView behaviour; you need to set the content size appropriately. TPKeyboardAvoiding includes a utility method that can do this for you, contentSizeToFit, but you need to call it yourself.
Ok. Thank you Michael. In fact, I was using it wrong. Now it works. :D
I have long scroll view with 12 tetxFiled on it. It scroll for few textfiled but after that it doesn’t scroll. I have set Content size manually and also call your function contentSizeToFit but nothing happen. I am using iOS 7. Can you help me for the same.